#include "../list.h"
#include "test_utilities.h"
#include <stdlib.h>

#define ASSERT_SUCCESS(__ARGS__) ASSERT_TEST((__ARGS__) == LIST_SUCCESS)

/** Function to be used for copying an int into the list */
static ListElement copyInt(ListElement n) {
	if (!n) {
		return NULL;
	}
	int* copy = malloc(sizeof(*copy));
	if (!copy) {
		return NULL;
	}
	*copy = *(int*) n;
	return copy;
}

/** Function to be used by the list for freeing elements */
static void freeInt(ListElement n) {
	free(n);
}

/** Function to be used by the list for freeing elements */
static int compareInts(ListElement n1, ListElement n2) {
	return *(int*) n1 - *(int*) n2;
}

/** Function to be used by the list for filtering elements */
static bool isNotInRange(ListElement n) {
	return !((*(int*) n > 4) && (*(int*) n < 14));
}

bool test_listCreate() {
	List list = listCreate(copyInt, freeInt);

	ASSERT_TEST(listGetSize(list) == 0);
	ASSERT_TEST(listGetFirst(list) == NULL);
	ASSERT_TEST(listGetCurrent(list) == NULL);
	ASSERT_TEST(listGetNext(list) == NULL);
	ASSERT_TEST(listGetCurrent(list) == listGetFirst(list));

	listDestroy(list);
	return true;
}

bool test_listCopy1() {
	List list = listCreate(copyInt, freeInt);
	List copy = listCopy(list);

	ASSERT_TEST(listGetSize(copy) == listGetSize(list));
	ASSERT_TEST(listGetFirst(copy) == listGetFirst(list));
	ASSERT_TEST(listGetCurrent(copy) == listGetCurrent(list));
	ASSERT_TEST(listGetNext(copy) == listGetNext(list));
	ASSERT_TEST(listGetCurrent(copy) == listGetCurrent(list));

	listDestroy(list);
	listDestroy(copy);
	return true;
}

bool test_listInsertFirst_listGetFirst() {
	int twenty = 20, three = 3;

	List list = listCreate(copyInt, freeInt);

	ASSERT_TEST(listInsertFirst(NULL, &three) == LIST_NULL_ARGUMENT);
	ASSERT_TEST(listInsertFirst(list, NULL) == LIST_NULL_ARGUMENT);
	ASSERT_SUCCESS(listInsertFirst(list, &twenty));

	ASSERT_SUCCESS(listInsertFirst(list, &three));
	ASSERT_SUCCESS(listInsertFirst(list, &twenty));
	ASSERT_TEST(*(int*)listGetFirst(list) == 20);
	ASSERT_TEST(*(int*)listGetNext(list) == 3);

	listDestroy(list);
	return true;
}

bool test_listInsertLast_listGetNext() {
	int five = 5, minusTwo = -2, twentySeven = 27;
	List list = listCreate(copyInt, freeInt);

	ASSERT_SUCCESS(listInsertLast(list, &five));
	ASSERT_SUCCESS(listInsertLast(list, &minusTwo));
	ASSERT_SUCCESS(listInsertLast(list, &five));
	ASSERT_SUCCESS(listInsertLast(list, &twentySeven));
	ASSERT_TEST(listInsertLast(NULL, &five) == LIST_NULL_ARGUMENT);
	ASSERT_TEST(listInsertLast(list, NULL) == LIST_NULL_ARGUMENT);
	ASSERT_TEST(listInsertLast(NULL, NULL) == LIST_NULL_ARGUMENT);

	ASSERT_TEST(listGetNext(list) == NULL);
	ASSERT_TEST(*(int*)listGetFirst(list) == 5);
	ASSERT_TEST(*(int*)listGetNext(list) == -2);
	ASSERT_TEST(*(int*)listGetNext(list) == 5);
	ASSERT_TEST(*(int*)listGetNext(list) == 27);
	ASSERT_TEST(listGetNext(NULL) == NULL);
	ASSERT_TEST(listGetNext(list) == NULL);

	listDestroy(list);
	return true;
}

bool test_listInsertBeforeCurrent() {
	int five = 5, minusTwo = -2, twentySeven = 27, twenty = 20;
	List list = listCreate(copyInt, freeInt);

	ASSERT_SUCCESS(listInsertFirst(list, &five));
	ASSERT_SUCCESS(listInsertLast(list, &minusTwo));
	ASSERT_TEST(listInsertBeforeCurrent(NULL, &five) == LIST_NULL_ARGUMENT);
	ASSERT_TEST(listInsertBeforeCurrent(list, NULL) == LIST_NULL_ARGUMENT);
	ASSERT_TEST(listInsertBeforeCurrent(list, &five) == LIST_INVALID_CURRENT);
	listGetFirst(list);

	ASSERT_TEST(listInsertBeforeCurrent(NULL, &five) == LIST_NULL_ARGUMENT);
	ASSERT_TEST(listInsertBeforeCurrent(list, NULL) == LIST_NULL_ARGUMENT);
	ASSERT_SUCCESS(listInsertBeforeCurrent(list, &twenty));
	listGetNext(list);
	ASSERT_SUCCESS(listInsertBeforeCurrent(list, &twentySeven));

	ASSERT_TEST(*(int*)listGetFirst(list) == 20);
	ASSERT_TEST(*(int*)listGetNext(list) == 5);
	ASSERT_TEST(*(int*)listGetNext(list) == 27);
	ASSERT_TEST(*(int*)listGetNext(list) == -2);
	ASSERT_TEST(listGetNext(list) == NULL);

	listDestroy(list);
	return true;
}

bool test_listGetSize() {
	int two = 2;
	List list = listCreate(copyInt, freeInt);
	ASSERT_TEST(listGetSize(list) == 0);
	listInsertFirst(list, &two);
	ASSERT_TEST(listGetSize(list) == 1);
	listInsertFirst(list, &two);
	ASSERT_TEST(listGetSize(list) == 2);
	listGetFirst(list);
	listRemoveCurrent(list);
	ASSERT_TEST(listGetSize(list) == 1);
	listClear(list);
	ASSERT_TEST(listGetSize(list) == 0);

	listDestroy(list);
	return true;
}

bool test_listCopy2() {
	int two = 2, three = 3, minusFive = -5, thirty = 30;
	List list = listCreate(copyInt, freeInt);
	listInsertLast(list, &two);
	listInsertLast(list, &three);
	listInsertLast(list, &minusFive);
	listInsertLast(list, &thirty);
	List copy = listCopy(list);

	ASSERT_TEST(listGetSize(copy) == listGetSize(list));
	ASSERT_TEST(*(int*)listGetFirst(copy) == *(int*)listGetFirst(list));
	ASSERT_TEST(*(int*)listGetNext(copy) == *(int*)listGetNext(list));
	ASSERT_TEST(*(int*)listGetNext(copy) == *(int*)listGetNext(list));
	ASSERT_TEST(*(int*)listGetNext(copy) == *(int*)listGetNext(list));

	listDestroy(list);
	listDestroy(copy);

	return true;
}

bool test_listRemoveCurrent() {
	int five = 5, minusTwo = -2, twentySeven = 27;
	List list = listCreate(copyInt, freeInt);

	ASSERT_SUCCESS(listInsertLast(list, &five));
	ASSERT_SUCCESS(listInsertLast(list, &minusTwo));
	ASSERT_SUCCESS(listInsertLast(list, &five));
	ASSERT_SUCCESS(listInsertLast(list, &twentySeven));
	listGetFirst(list);
	listGetNext(list);
	listGetNext(list);
	ASSERT_SUCCESS(listRemoveCurrent(list)); //Delete the third element
	ASSERT_TEST(listGetCurrent(list) == NULL);
	listGetFirst(list);
	ASSERT_SUCCESS(listRemoveCurrent(list)); //Delete the first element

	ASSERT_TEST(listGetSize(list) == 2);
	ASSERT_TEST(*(int*)listGetFirst(list) == -2);
	ASSERT_TEST(*(int*)listGetNext(list) == 27);

	listDestroy(list);
	return true;
}

bool test_listGetCurrent() {
	int two = 2, three = 3, minusFive = -5;
	List list = listCreate(copyInt, freeInt);
	listInsertLast(list, &two);
	listInsertLast(list, &three);
	listInsertLast(list, &minusFive);

	listGetFirst(list);
	ASSERT_TEST(*(int*)listGetCurrent(list) == 2);
	listGetNext(list);
	ASSERT_TEST(*(int*)listGetCurrent(list) == 3);
	ASSERT_TEST(listGetCurrent(NULL) == NULL);
	listGetNext(list);
	ASSERT_TEST(*(int*)listGetCurrent(list) == -5);
	listGetNext(list);
	ASSERT_TEST(listGetCurrent(list) == NULL);

	listDestroy(list);
	return true;
}
bool test_listGetNext() {
	int two = 2, three = 3, minusFive = -5;
	List list = listCreate(copyInt, freeInt);
	listInsertLast(list, &two);
	listInsertLast(list, &three);
	listInsertLast(list, &minusFive);

	ASSERT_TEST(listGetNext(NULL) == NULL);
	ASSERT_TEST(listGetNext(list) == NULL);
	listGetFirst(list);
	ASSERT_TEST(*(int*)listGetNext(list) == 3);
	ASSERT_TEST(*(int*)listGetNext(list) == -5);
	ASSERT_TEST(listGetNext(list) == NULL);

	listDestroy(list);
	return true;
}

bool test_listSort() {
	int twenty = 20, zero = 0, five = 5, minusTwo = -2, minusEight = -8;
	List list = listCreate(copyInt, freeInt);
	listInsertLast(list, &twenty);
	listInsertLast(list, &minusTwo);
	listInsertLast(list, &five);
	listInsertLast(list, &zero);
	listInsertLast(list, &minusEight);

	ASSERT_SUCCESS(listSort(list, compareInts));
	ASSERT_TEST(listGetSize(list) == 5);
	ASSERT_TEST(*(int*)listGetFirst(list) == -8);
	ASSERT_TEST(*(int*)listGetNext(list) == -2);
	ASSERT_TEST(*(int*)listGetNext(list) == 0);
	ASSERT_TEST(*(int*)listGetNext(list) == 5);
	ASSERT_TEST(*(int*)listGetNext(list) == 20);

	listDestroy(list);
	return true;
}

bool test_listFilter() {
	int twenty = 20, ten = 10, five = 5, minusTwo = -2, minusEight = -8;
	List list = listCreate(copyInt, freeInt);
	listInsertLast(list, &twenty);
	listInsertLast(list, &minusTwo);
	listInsertLast(list, &five);
	listInsertLast(list, &ten);
	listInsertLast(list, &minusEight);

	List filteredList = listFilter(list, isNotInRange);
	ASSERT_TEST(listGetSize(filteredList) == 3);
	ASSERT_TEST(*(int*)listGetFirst(filteredList) == 20);
	ASSERT_TEST(*(int*)listGetNext(filteredList) == -2);
	ASSERT_TEST(*(int*)listGetNext(filteredList) == -8);

	listDestroy(list);
	listDestroy(filteredList);
	return true;
}

bool test_listClear() {
	int twenty = 20, five = 5, minusTwo = -2;
	List list = listCreate(copyInt, freeInt);
	listInsertLast(list, &twenty);
	listInsertLast(list, &minusTwo);
	listInsertLast(list, &five);
	ASSERT_TEST(listGetSize(list) == 3);
	ASSERT_SUCCESS(listClear(list));
	ASSERT_TEST(listGetSize(list) == 0);

	listDestroy(list);
	return true;
}

bool test_listDestroy() {
	/* tested via valgrind */
	return true;
}

int main() {
	RUN_TEST(test_listCreate);
	RUN_TEST(test_listCopy1);
	RUN_TEST(test_listInsertFirst_listGetFirst);
	RUN_TEST(test_listInsertLast_listGetNext);
	RUN_TEST(test_listInsertBeforeCurrent);
	RUN_TEST(test_listGetSize);
	RUN_TEST(test_listCopy2);
	RUN_TEST(test_listRemoveCurrent);
	RUN_TEST(test_listGetCurrent);
	RUN_TEST(test_listGetNext);
	RUN_TEST(test_listSort);
	RUN_TEST(test_listFilter);
	RUN_TEST(test_listClear);
	RUN_TEST(test_listDestroy);

	return 0;
}
